/**
 *  This is the base class for all database drivers. Its role is to contain all driver-specific information, such as
 *  how to encode/decode its instances, and how to add/update/remove/query etc.
 *  
 *  For anyone that wants to add another driver, look out for this class, as it's a little tricky.
 *  The problem is that it's present on both macro and runtime code. But we don't want to add the runtime
 *  dependencies to the macro side, but instead use everything that extends this class as only a
 *  way to serialize the instance own arguments, so we can recreate the class on the runtime.
 *  
 *  @author waneck
 */
package time.providers.base;
#if !macro
import time.types.ds.KeyStore;
import time.runtime.ObjectStore;
import time.types.ds.Future;
import time.types.ds.Key;
import time.Errors;
import time.types.ds.KeyCache;
import time.types.ds.ObjectHash;

#end

@:generatorClass("time.providers.base.compiler.Generator")
class Driver #if !macro implements ObjectStore, implements AsyncObjectStore #end
{
	private var baseExtensions:Array<String>;
	private var generatorClass:String;
	
	public function new() : Void
	{
		init();
	}
	
	private function init()
	{
		//ALWAYS put code initialization in here
		generatorClass = "time.providers.base.compiler.Generator";
#if !macro
		keyStore = new KeyCache();
#end
	}
	
	public function getGeneratorClass() : Class<Dynamic>
	{
		var ret = Type.resolveClass(generatorClass);
		if (null == ret)
			throw "Generator class " + generatorClass + " not found; Are you sure you've added --macro path.to.your.Config() to the build file?";
		return ret;
	}
	
	
	public function extensions()
	{
		return (null != baseExtensions) ? baseExtensions.copy() : [];
	}
	
	///////////////////////////////////////
	// CAPACITIES
	///////////////////////////////////////
	
	public function supportsReferences() : Bool
	{
		return false;
	}
	
	///////////////////////////////////////
	// OBJECT STORE
	///////////////////////////////////////
	
#if !macro
	/**
	*  Sets the depth of activated objects
	**/
	public var activationDepth(get_activationDepth, set_activationDepth):Int;
	
	/**
	*  The key cache
	**/
	private var keyStore:KeyStore;
	
	/**
	*  Async operations. Warning - not fully supported by all platforms
	**/
	public var async(get_async, never):AsyncObjectStore;
	
	//var decoder:Decoder;
	
	function get_async()
	{
		return this;
	}
	
	function get_activationDepth()
	{
		return activationDepth;
	}
	
	function set_activationDepth(v:Int)
	{
		return activationDepth = v;
	}
	
	//send query (won't post here because types will vary greatly from implementation)
	
	//won't receive any argument as all driver-specific infos are already defined
	public function open() : Void
	{
		throw NotImplemented;
	}
	
	public function isOpen() : Bool
	{
		throw NotImplemented;
		return null;
	}
	
	public function close() : Void
	{
		throw NotImplemented;
	}
	
	public function dispose(obj : Dynamic) : Bool
	{
		return keyStore.dispose(obj);
		return null;
	}
	
	public function insert<T>(obj : T, errorIfExists : Bool = false) : Key<T>
	{
		var k = keyStore.getKey(obj);
		if (null != k)
		{
			if (errorIfExists) throw "Error on insert: Object already is inserted with key " + k;
			update(obj);
			
			return k;
		} else {
			return insertImpl(obj, errorIfExists);
		}
	}
	
	private function insertImpl<T>(obj : T, errorIfExists : Bool = false) : Key<T>
	{
		throw NotImplemented;
		return null;
	}
	
	public function insertAll<T>(objs : Iterable<T>, errorIfExists : Bool = false) : ObjectHash<T, Key<T>>
	{
		var ohash = new ObjectHash<T, Key<T>>();
		for (o in objs)
		{
			if (!ohash.exists(o))
				ohash.set(o, insert(o, errorIfExists));
		}
		
		return ohash;
	}
	
	public function update<T>(obj : T) : Void
	{
		var k = keyStore.getKey(obj);
		if (null == k)
		{
			throw "Error on update: Key not found for object in this object storage.";
		}
		
		updateImpl(obj, k);
	}
	
	private function updateImpl<T>(obj : T, key : Key<T>) : Void
	{
		throw NotImplemented;
	}
	
	public function updateAll<T>(objs : Iterable<T>) : Void
	{
		for (o in objs)
			update(o);
	}
	
	public function sync(obj : Dynamic) : Void
	{
		var key = keyStore.getKey(obj);
		if (null == key)
			throw "Error on sync: Object not stored in this ObjectStore";
		
		syncImpl(obj, key);
	}
	
	private function syncImpl(obj : Dynamic, key : Key<Dynamic>) : Void
	{
		throw NotImplemented;
	}
	
	public function syncAll(objs : Iterable<Dynamic>) : Void
	{
		for (o in objs)
			sync(o);
	}
	
	public function delete<T>(obj : T) : Bool
	{
		var key = keyStore.getKey(obj);
		if (null == key)
			throw "Error on delete: Object not stored in this ObjectStore";
		
		return deleteImpl(obj, key);
	}
	
	private function deleteImpl<T>(obj : T, key : Key<T>) : Bool
	{
		throw NotImplemented;
		return false;
	}
	
	public function get<T>(key : Key<T>) : T
	{
		var o = keyStore.getWithKey(key);
		return if (null == o)
			getImpl(key);
		else
			o;
	}
	
	private function getImpl<T>(key : Key<T>) : T
	{
		throw NotImplemented;
		return null;
	}
	
	public function getWith<T>(c : Class<T>, key : String) : T
	{
		throw NotImplemented;
		return null;
	}
	
	//this will clean up all cache used
	public function cleanup() : Void
	{
		this.keyStore = new KeyCache();
	}
	
	//just a helper function to avoid repeating. It's inline as it seems to me
	//that non-inline would actually generate more garbage code on some targets
	//because of the anonymous function
	inline function future<T>(f:Void->T):Future<T>
	{
		/*
		try
		{
			var v = f();
			return new Future().deliver(v);
		}
		catch (e:Dynamic)
		{
			return new Future().deliverException(e);
		}*/
		//TODO make this work
		return null;
	}
	
	//async implementation is at this point only calling 
	
	public function insertAsync<T>(obj : T, errorIfExists : Bool = false) : Future<Key<T>>
	{
		return future(function() return insert(obj, errorIfExists));
	}
	
	public function insertAllAsync<T>(objs : Iterable<T>, errorIfExists : Bool = false) : Future<ObjectHash<T, Key<T>>>
	{
		return future(function() return insertAll(objs, errorIfExists));
	}
	
	public function updateAsync<T>(obj : T) : Future<Void>
	{
		return future(function() return update(obj));
	}
	
	public function updateAllAsync<T>(objs : Iterable<T>) : Future<Void>
	{
		return future(function() return updateAll(objs));
	}
	
	public function deleteAsync<T>(obj : T) : Future<Bool>
	{
		return future(function() return delete(obj));
	}
	
	public function getAsync<T>(key : Key<T>) : Future<T>
	{
		return future(function() return get(key));
	}
	
	public function getWithAsync<T>(c : Class<T>, key : String) : Future<T>
	{
		return future(function() return getWith(c, key));
	}
	
	public function closeAsync() : Future<Void>
	{
		return future(function() return close());
	}
	
	public function openAsync() : Future<Void>
	{
		return future(function() return open());
	}
	
	
#end
}